/*-------------------------------------------------------------------------
 *
 * psprintf.c
 *		sprintf到一个按需分配的缓冲区
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/common/psprintf.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND

#include "postgres.h"

#include "utils/memutils.h"

#else

#include "postgres_fe.h"

/* 我们可能在前端代码中使用不同的值 */
#define MaxAllocSize	((Size) 0x3fffffff) /* 1 gigabyte - 1 */

#endif


/*
 * psprintf
 *
 * 在 fmt（一个 sprintf 样式的格式字符串）的控制下格式化文本数据
 * 并将其返回到一个按需分配的缓冲区中。缓冲区是在后端使用 palloc 分配的，
 * 或在前端构建中使用 malloc。调用者有责任在不再需要时释放缓冲区（如果合适）。
 *
 * 错误不会返回给调用者，而是在后端通过 elog(ERROR) 报告，
 * 或在前端构建中通过 printf-to-stderr-and-exit() 报告。
 * 因此，使用此方法时在 libpq 中需要三思而后行。
 */
char * psprintf(const char *fc_fmt,...)
{
	int			fc_save_errno = errno;
	size_t		fc_len = 128;		/* 关于缓冲区大小的初始假设 */

	for (;;)
	{
		char	   *fc_result;
		va_list		fc_args;
		size_t		fc_newlen;

		/*
		 * 分配结果缓冲区。注意在前端这映射到 malloc
		 * 且在错误时退出。
		 */
		fc_result = (char *) palloc(fc_len);

		/* 尝试格式化数据。 */
		errno = fc_save_errno;
		va_start(fc_args, fc_fmt);
		fc_newlen = pvsnprintf(fc_result, fc_len, fc_fmt, fc_args);
		va_end(fc_args);

		if (fc_newlen < fc_len)
			return fc_result;		/* 成功 */

		/* 释放缓冲区并循环以尝试更大的 len。 */
		pfree(fc_result);
		fc_len = fc_newlen;
	}
}

/*
 * pvsnprintf
 *
 * 尝试在 fmt（一个 sprintf 样式的格式字符串）的控制下格式化文本数据
 * 并将其插入 buf（其长度为 len）。
 *
 * 如果成功，返回输出的字节数，不包括结尾的零字节。这个值总是严格小于 len。
 *
 * 如果 buf 中没有足够的空间，返回成功所需的缓冲区大小的估计值
 * （这 *必须* 大于给定的 len，否则调用者可能会无限循环）。
 *
 * 其他错误情况不会返回，而是通过 elog(ERROR) 或 exit() 退出。
 * 因此，这不应该在 libpq 中使用。
 *
 * 注意：调用者在循环时必须确保保留其入口时的 errno
 * 以防 fmt 中包含 "%m"。
 *
 * 请注意，返回值的语义并不完全符合 C99。
 * 首先，我们不保证估计的缓冲区大小是完全正确的；
 * 调用者必须准备循环多次以获得正确的大小。
 * （如果有一个符合 C99 的 vsnprintf，应该不会发生这种情况，
 * 但据说某些实现并不总是返回相同的值...）
 * 其次，我们返回推荐的缓冲区大小，而不是比该大小小一。
 * 这让溢出问题可以在这里处理，而不是在调用者中处理。
 */
size_t pvsnprintf(char *fc_buf, size_t fc_len, const char *fmt, va_list fc_args)
{
	int			fc_nprinted;

	fc_nprinted = vsnprintf(fc_buf, fc_len, fmt, fc_args);

	/* 我们假设失败意味着 fmt 是无效的，因此硬失败是可以的 */
	if (unlikely(fc_nprinted < 0))
	{
#ifndef FRONTEND
		elog(ERROR, "vsnprintf failed: %m with format string \"%s\"", fmt);
#else
		fprintf(stderr, "vsnprintf failed: %s with format string \"%s\"\n",
				strerror(errno), fmt);
		exit(EXIT_FAILURE);
#endif
	}

	if ((size_t) fc_nprinted < fc_len)
	{
		/* 成功。注意 nprinted 不包括尾随空字符。 */
		return (size_t) fc_nprinted;
	}

	/*
	 * 我们假设使用一个符合 C99 的 vsnprintf，因此相信它对
	 * 所需空间的估计，并为尾随空字符增加一个。 （如果它错误，
	 * 逻辑仍然会工作，但我们可能会循环多次。）
	 *
	 * 如果所需空间会超过 MaxAllocSize，则中止。注意即使在前端
	 * 我们也使用这个以 palloc 为导向的溢出限制。
	 */
	if (unlikely((size_t) fc_nprinted > MaxAllocSize - 1))
	{
#ifndef FRONTEND
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("out of memory")));
#else
		fprintf(stderr, _("out of memory\n"));
		exit(EXIT_FAILURE);
#endif
	}

	return fc_nprinted + 1;
}
